package flow

import (
	"context"
	"errors"
	"fmt"
	"github.com/dop251/goja"
	"github.com/gammazero/workerpool"
	"github.com/tidwall/gjson"
	"nextFlow/types"
	"nextFlow/utils"
	"strings"
)

type FlowStruct struct {
	Ctx        context.Context
	Wp         *workerpool.WorkerPool
	Nodes      map[string]types.Node
	DebugFuc   func(v ...interface{})
	TriggerFuc func(v ...interface{})
	Config     types.H
	jsEngine   *utils.GojaJsEngine
}

func New(configArr ...types.H) *FlowStruct {

	s := &FlowStruct{
		Wp:    workerpool.New(1),
		Ctx:   context.TODO(),
		Nodes: make(map[string]types.Node),
	}

	s.jsEngine = utils.NewGojaJsEngine("", "", nil)

	if len(configArr) == 1 {
		s.Config = configArr[0]
	}

	return s
}

func (s *FlowStruct) Expr(str string) string {
	out, err := s.jsEngine.RunString(fmt.Sprintf(`(function(){ return %s; })()`, str))
	if err == nil {
		if out.(goja.Value).Export().(bool) {
			return types.True
		}
		return types.False
	}
	return types.Fail
}

func (s *FlowStruct) JsDo(config types.H) (outFunc interface{}, outTrigger interface{}, err error) {

	jsPath := config.Get("src").String()
	funcName := config.Get("func").String()

	var triggerName = ""

	if config.Get("trigger").Exists() {
		triggerName = "trigger"
	}

	var params []goja.Value
	var initParams []goja.Value
	var triggerParams []goja.Value

	entrypoint, e := s.jsEngine.Execute("require", jsPath)

	if e != nil {
		return nil, nil, e
	}

	mainjs, ok := entrypoint.(*goja.Object)

	if !ok {
		return nil, nil, errors.New("require 模块失败")
	}

	init, ok := goja.AssertFunction(mainjs.Get("init"))

	if !ok {
		return nil, nil, errors.New("init is not a function")
	}

	for _, initParamsKey := range utils.ParseJSFunctionParams(mainjs.Get("init").ToString().String()) {
		initParams = append(initParams, s.jsEngine.ToValue(config.Get(fmt.Sprintf("init.%s", initParamsKey)).Value()))
	}

	init(mainjs, initParams...)

	// 如果有触发器就执行触发器
	if triggerName != "" {

		trigger, ok := goja.AssertFunction(mainjs.Get(triggerName))

		if !ok {
			return nil, nil, errors.New(triggerName + " is not a function")
		}

		for _, triggerParamsKey := range utils.ParseJSFunctionParams(mainjs.Get(triggerName).ToString().String()) {
			triggerParams = append(triggerParams, s.jsEngine.ToValue(config.Get(fmt.Sprintf("trigger.%s", triggerParamsKey)).Value()))
		}

		resTrigger, err := trigger(mainjs, triggerParams...)

		if err != nil {
			return nil, nil, err
		}

		outTrigger = resTrigger.Export()

	}

	main, ok := goja.AssertFunction(mainjs.Get(funcName))

	if !ok {
		return nil, nil, errors.New(funcName + " is not a function")
	}

	for _, mainParamsKey := range utils.ParseJSFunctionParams(mainjs.Get(funcName).ToString().String()) {
		params = append(params, s.jsEngine.ToValue(config.Get(fmt.Sprintf("params.%s", mainParamsKey)).Value()))
	}

	resFunc, err := main(mainjs, params...)

	if err == nil {

		outFunc = resFunc.Export()

		return

	}

	return nil, nil, err

}

func (s *FlowStruct) ExprCalc(str string, id ...string) string {

	ruleStr := utils.SprintfVarFunc(str, func(matched string) interface{} {
		p := strings.Split(matched, ".")
		if p[0] == "nodes" {
			index := p[1]
			path := strings.Join(p[2:len(p)], ".")
			result := s.Nodes[index].ConfigGet(path)
			return result.Value()
		} else if len(id) == 1 {
			result := s.Nodes[id[0]].ConfigGet(matched)
			return result.Value()
		}
		return matched
	})

	return s.Expr(ruleStr)

}

func (s *FlowStruct) Debug(cbDebug func(...interface{})) *FlowStruct {
	s.DebugFuc = cbDebug
	return s
}

func (s *FlowStruct) Trace(v ...interface{}) {
	if s.DebugFuc == nil {
		fmt.Println(v...)
	} else {
		s.DebugFuc(v...)
	}
}

func (s *FlowStruct) AddNode(nodes ...types.Node) *FlowStruct {
	for _, node := range nodes {
		node.SetFlowCtx(s)
		s.Nodes[node.Index()] = node
	}
	return s
}

func (s *FlowStruct) SubmitTack(task func()) {
	s.Wp.Submit(task)
}

func (s *FlowStruct) GetNextNodes(index string, cond string) ([]types.Node, bool) {

	var nodes []types.Node

	s.Nodes[index].ConfigGet("children").ForEach(func(key, value gjson.Result) bool {
		to := value.Get("to").String()
		condStr := value.Get("cond").String()
		if cond == condStr {
			nodes = append(nodes, s.Nodes[to])
		}
		return true
	})

	if len(nodes) > 0 {
		return nodes, true
	} else {
		return nil, false
	}

}

func (s *FlowStruct) Start(v types.H) *FlowStruct {

	s.Nodes["1"].ConfigSet("inputs.trigger.playload", v)
	s.Nodes["1"].ConfigSet("outputs", types.H{})
	err := s.Nodes["1"].OnHandle()

	if err == nil {

		s.SubmitTack(func() {
			s.Nodes["1"].HandleNext("")
		})

	}

	return s
}

func (s *FlowStruct) Trigger(v ...interface{}) any {

	if len(v) == 1 {

		cbTrigger, ok := v[0].(func(...interface{}))
		if ok {
			s.TriggerFuc = cbTrigger
			return s
		}

	}

	if s.TriggerFuc != nil {
		s.TriggerFuc(v...)
	}

	return s
}

func (s *FlowStruct) CetContext() context.Context {
	return s.Ctx
}

func (s *FlowStruct) SetContext(c context.Context) *FlowStruct {
	s.Ctx = c
	return s
}

func (s *FlowStruct) End(cbArr ...func()) {

	s.Wp.StopWait()

	if len(cbArr) == 1 {
		cbArr[0]()
	}
}
